/*
 * Copyright (C) 2016 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { RealCall } from '../connection/RealCall'
import { Chain, Interceptor } from '../Interceptor'
import { List } from '@kit.ArkTS'
import { Request } from '../Request'
import { Exchange } from '../connection/Exchange'
import { Connection } from '../Connection'
import { checkDuration, kotlinCheck } from '../utils/Utils'
import { TimeUnit } from '../utils/TimeUnit'
import { Call } from '../Call'
import { Response } from '../Response'
import { IllegalArgumentException, IOException } from '../Error/error'

/**
 * A concrete interceptor chain that carries the entire interceptor chain: all application
 * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
 *
 * If the chain is for an application interceptor then [exchange] must be null. Otherwise it is for
 * a network interceptor and [exchange] must be non-null.
 */
export class RealInterceptorChain implements Chain {
  #call: RealCall
  interceptors: List<Interceptor>
  index: number
  #exchange?: Exchange
  #request: Request
  #connectTimeoutMillis: number
  #readTimeoutMillis: number
  #writeTimeoutMillis: number
  constructor(call: RealCall,interceptors: List<Interceptor>, index: number, exchange: Exchange, request: Request, connectTimeoutMillis: number, readTimeoutMillis: number, writeTimeoutMillis: number) {
    this.#call = call
    this.interceptors = interceptors
    this.index = index
    this.#exchange = exchange
    this.#request = request
    this.#connectTimeoutMillis = connectTimeoutMillis
    this.#readTimeoutMillis = readTimeoutMillis
    this.#writeTimeoutMillis = writeTimeoutMillis
  }
  private calls: number = 0

  copy(index: number = this.index,
    exchange: Exchange | undefined = this.#exchange,
    request: Request = this.#request,
    connectTimeoutMillis: number = this.#connectTimeoutMillis,
    readTimeoutMillis: number = this.#readTimeoutMillis,
    writeTimeoutMillis: number = this.#writeTimeoutMillis
  ): RealInterceptorChain{
    return new RealInterceptorChain(this.#call, this.interceptors, index, exchange, request, connectTimeoutMillis,
      readTimeoutMillis, writeTimeoutMillis)
  }

  connection(): Connection | null {
    return this.#exchange?.connection
  }

  connectTimeoutMillis(): number {
    return this.#connectTimeoutMillis
  }

  withConnectTimeout(timeout: number, unit: TimeUnit): Chain {
    kotlinCheck(this.#exchange === undefined, "Timeouts can't be adjusted in a network interceptor")

    let connectTimeoutMillis = checkDuration("connectTimeout", timeout, unit)
    return this.copy(undefined, undefined, undefined, connectTimeoutMillis, undefined, undefined)
  }

  readTimeoutMillis(): number {
    return this.#readTimeoutMillis
  }

  withReadTimeout(timeout: number, unit: TimeUnit): Chain {
    kotlinCheck(this.#exchange === undefined, "Timeouts can't be adjusted in a network interceptor")

    let readTimeoutMillis = checkDuration("readTimeout", timeout, unit)
    return this.copy(undefined, undefined, undefined, undefined, readTimeoutMillis, undefined)
  }

  writeTimeoutMillis(): number{
    return this.#writeTimeoutMillis
  }

  withWriteTimeout(timeout: number, unit: TimeUnit): Chain {
    kotlinCheck(this.#exchange === undefined, "Timeouts can't be adjusted in a network interceptor")

    let writeTimeoutMillis = checkDuration("writeTimeout", timeout, unit)
    return this.copy(undefined, undefined, undefined, undefined, undefined, writeTimeoutMillis)
  }

  call(): Call{
    return this.#call
  }

  request(): Request {
    return this.#request
  }

  exchange(): Exchange {
    return this.#exchange
  }

  // @Throws(IOException::class)
  async proceed(request: Request): Promise<Response> {
    kotlinCheck(this.index < this.interceptors.length, '')

    this.calls++
    if (this.#exchange !== undefined) {
      kotlinCheck(this.#exchange.finder.sameHostAndPort(request.url),`network interceptor ${this.interceptors[this.index - 1]} must retain the same host and port`)
      kotlinCheck(this.calls === 1, `network interceptor ${this.interceptors.get(this.index - 1)} must call proceed() exactly once`)
    }

    // Call the next interceptor in the chain.
    let next = this.copy(this.index + 1, undefined, request, undefined, undefined, undefined)
    let interceptor: Interceptor = this.interceptors.get(this.index)

    // @Suppress("USELESS_ELVIS")
    let response = await interceptor.intercept(next)
    if (response === null || response === undefined) {
      throw new IOException(`interceptor ${interceptor} returned null`)
    }
    if (this.#exchange !== undefined) {
      kotlinCheck(this.index + 1 >= this.interceptors.length || next.calls === 1,`network interceptor ${interceptor} must call proceed() exactly once`)
    }

    kotlinCheck(response.body !== null,`interceptor ${interceptor} returned a response with no body`)
    return response
  }
}